## Магический метод `__new__`. Пример паттерна `Singleton`


 Познакомимся с еще одним магическим методом `__new__`, который вызывается непосредственно перед созданием объекта класса. 
 
 Напомню, что другой магический метод `__init__` вызывается после создания объекта (о нем мы говорили на предыдущем занятии).

Здесь у вас может сразу возникнуть вопрос, зачем нужно было определять два разных метода, которые последовательно вызываются при создании экземпляров классов? 

Разве не достаточно одного `__init__`, чтобы выполнять начальную инициализацию объекта? 

Конечно, нет.

В практике программирования встречаются самые разнообразные задачи и иногда нужно что-то делать и до создания объектов. 

Например, реализация известного паттерна `Singleton` в `Python`, как раз делается через метод `__new__` и мы с ним позже познакомимся.

А для начала нам нужно познакомиться с работой самого метода `__new__`. 

Давайте добавим его в наш класс `Point`. Я его перепишу в сокращенной форме:

```python
class Point:
    def __new__(cls, *args, **kwargs):
        print("вызов __new__ для " + str(cls))
 
    def __init__(self, x=0, y=0):
        print("вызов __init__ для " + str(self))
        self.x = x
        self.y = y
```

Смотрите, здесь записан метод `__new__`, у которого первым идет обязательный параметр `cls` – это ссылка на текущий класс `Point`, а затем, указываются коллекции из фактических и формальных параметров, которые может принимать данная функция. 

Это стандартное определение метода `__new__` в классах. 

В теле функции я просто сделал вывод сообщения и переменной `cls`.

Если теперь попробовать создать экземпляр класса:

> pt = Point(1, 2)

то мы в консоли увидим только одно сообщение от метода `__new__`. 

То есть, второй метод `__init__` не был вызван и, кроме того, если мы распечатаем переменную `pt`:

> print(pt)

то увидим значение `None`, то есть, объект не был создан. Почему так произошло? 

В Python магический метод `__new__` должен возвращать адрес нового созданного объекта.

А в нашей программе он ничего не возвращает, то есть, значение None, что эквивалентно отказу в создании нового объекта. Именно поэтому переменная `pt` принимает значение `None`.

Хорошо, давайте адрес нового объекта.

Но откуда мы его возьмем? Для этого можно вызвать аналогичный метод базового класса и делается это, следующим образом:

```python
    def __new__(cls, *args, **kwargs):
        print("вызов __new__ для " + str(cls))
        return super().__new__(cls)
```

Здесь функция `super()` возвращает ссылку на базовый класс и через нее мы вызываем метод `__new__` с одним первым аргументом. Но, подождите! Что это за базовый класс? 

Мы наш класс `Point` ни от какого класса не наследовали? Да и вообще еще не изучали тему наследования!

Да, поэтому что, начиная с версии Python 3, все классы автоматически и неявно наследуются от базового класса `object`:


И уже из этого базового класса мы вызываем метод `__new__`. Кстати, если метод `__new__` не прописывать в классе Point, то будет автоматически запускаться версия базового класса.

То есть, этот метод всегда вызывается при создании нового объекта.

При необходимости, мы можем его переопределять, добавляя новую логику его работы.

И то же самое относится ко всем магическим методам. Они всегда существуют у всех классов. 

Но переопределяем мы лишь те, что необходимо, а остальные работают по умолчанию. 

В этом сила базового класса `object`. В нем уже существует программный код, общий для всех классов языка `Python`.

Иначе, нам пришлось бы его каждый раз прописывать заново.

Итак, теперь мы знаем откуда берется и вызывается магический метод `__new__`. 

Запустим программу и видим в консоли, что были вызваны оба метода `__new__` и `__init__` нашего класса `Point`, а также был успешно сформирован новый объект.

Возможно, здесь у вас остался один вопрос: а зачем нужны списки параметров `*args`, `**kwargs` в методе `__new__`? 

Мы, вроде, их нигде не используем? В действительности, здесь хранятся дополнительные параметры, которые мы можем указывать при создании объекта. 

Например, строчка:

> pt = Point(1, 2)
создает объект с двумя числовыми значениями, то есть, *args будет содержать эти два числа. 

По идее, мы можем реализовать в методе `__new__` какую-либо логику с учетом значений этих аргументов.

Но, в данном случае, просто игнорируем. Используем их дальше в методе `__init__` при инициализации объекта. То есть, аргументы 1 и 2 передаются и в метод `__new__` и в метод `__init__`.

### Пример паттерна Singleton (учебный)

Думаю, вы в целом теперь представляете себе работу магического метода `__new__`, но остается вопрос: зачем все же он нужен? 

В качестве ответа я приведу пример очень известного паттерна проектирования под названием `Singleton`. 

Этот паттерн будет представлен в учебном варианте, то есть, мы его реализуем не полностью, т.к. пока отсутствуют достаточные знания.

Итак, давайте предположим, что мы разрабатываем класс для работы с БД. 

В частности, через него можно будет подключаться к СУБД, читать и записывать информацию, закрывать соединение:
```python
class DataBase:
    def __init__(self, user, psw, port):
        self.user = user
        self.psw = psw
        self.port = port
 
    def connect(self):
        print(f"соединение с БД: {self.user}, {self.psw}, {self.port}")
 
    def close(self):
        print("закрытие соединения с БД")
 
    def read(self):
        return "данные из БД"
 
    def write(self, data):
        print(f"запись в БД {data}")
```

И далее полагаем, что в программе должен существовать только один экземпляр этого класса в каждый момент ее работы.

То есть, одновременно два объекта класса DataBase быть не должно. 

Чтобы это обеспечить и гарантировать, как раз и используется паттерн `Singleton`.

Реализуем его для класса `DataBase`.

Я пропишу в нем специальный атрибут (на уровне класса):

> __instance = None

который будет хранить ссылку на экземпляр этого класса. Если экземпляра нет, то атрибут будет принимать значение `None`.

А, затем, чтобы гарантировать создание строго одного экземпляра, добавим в класс магический метод `__new__`:

```python
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
 
        return cls.__instance
```

Работает этот метод очевидным образом. 
Мы проверяем атрибут класса `__instance`. 

Причем, для обращения к нему используем параметр `cls` – ссылку на текущий класс.

Подробнее я еще освещу этот момент. 

Далее, проверяем, если значение равно `None`, то вызываем метод `__new__` базового класса и тем самым разрешаем создание объекта. 

Иначе, просто возвращаем ссылку на ранее созданный экземпляр. Как видите, все достаточно просто.

И пропишем еще один магический метод – финализатор `__del__`, который будет обнулять атрибут `__instance` перед уничтожением объекта, чтобы мы могли, при необходимости, создать новый.

Все, простейший вариант паттерна `Singleton` готов. 

Правда он имеет один изъян. Смотрите, если попробовать создать два экземпляра:

```python
db = DataBase('root', '1234', 80)
db2 = DataBase('root2', '5678', 40)
print(id(db), id(db2))
```
то их `id` ожидаемо будут равны.

То есть, ссылки `db` и `db2` действительно ведут на один объект. 

Но, если выполнить метод:

```python
db.connect()
db2.connect()
```

то увидим значения: `'root2', '5678', 40` – аргументы при повторном создании класса.

По идее, если объект не создается, то и локальные свойства его также не должны меняться. 

Почему так произошло? Все просто. Мы здесь действительно видим первый объект. 

Но при повторном вызове `DataBase()` также был вызван магический метод `__init__` с новым набором аргументов и локальные свойства изменили свое значение. 

Конечно, мы можем здесь поставить «костыль» и дополнительно в классе прописать флаговый атрибут, например:

```python
__is_exist = False
```
специально для метода `__init__`, чтобы не выполнять его если объект уже создан. 

Но я даже не буду дописывать такую программу.

Слишком уж костыльно получается. 

Правильнее было бы здесь переопределить еще один магический метод `__call__`, о котором мы еще будем говорить. 

А пока оставим нашу реализацию паттерна `Singleton` в таком виде.

Я, надеюсь, что из этого занятия вы поняли, как работает магический метод `__new__` и зачем он нужен. 

Синглетон (одиночка) - один игрок (однопользователсьская игра), одна игра (один класс Game, но  много игроков, одно голосование и т.д.)

Если все это понятно, то переходим к следующему материалу.


